package xj.graph2d.shapes;

import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.StringTokenizer;

import org.w3c.dom.Element;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.RectangleShape;
import xj.graph2d.StrokeMap;
import xj.lang.CodeWriter;
import xj.util.xml.XMLUtil;

public class VerticalCompartmentsAdapter 
  implements VerticalCompartments, Cloneable, DrawShapeConstants {

  public VerticalCompartmentsAdapter(DrawShape owner) {
    this.owner = owner;
  }

  @Override
  public Object clone() {
    VerticalCompartmentsAdapter vc = new VerticalCompartmentsAdapter(owner);
    copyDividers(vc);
    vc.lineMode = lineMode;
    vc.lineStyle = lineStyle;
    return vc;
  }

  public void copyDividers(VerticalCompartmentsAdapter vc) {
    if (vc != null) { 
      if (dividers != null) {
	int n = dividers.length;
	float[] divs = new float[n];
	for (int i = 0; i < n; i++) {
	  divs[i] = dividers[i];
	}
	vc.setVerticalDividers(divs);
      }
      vc.compartmentNumberFixed = compartmentNumberFixed;
    }
  }

  public void setDividerStyle(DividerLineMode lineMode, int lineStyle) {
    this.lineMode = lineMode;
    this.lineStyle = lineStyle;
  }

  public void setHighLightCompartments(boolean highlight) { 
    this.highlight = highlight;
  }

  public boolean isHighLightCompartments() { 
    return highlight;
  }

  public boolean isExpandable() {
    return expandable;
  }

  public void setExpandable(boolean b) {
    expandable = b;
  }

  public boolean isCollapsable() {
    return collapsable;
  }

  public void setCollapsable(boolean b) {
    collapsable = b;
  }

  public DrawShape getOwner() {
    return owner;
  }

  public void setOwner(DrawShape owner) {
    this.owner = owner;
  }

  public float[] getVerticalDividers() {
    return dividers;
  }

  public void setVerticalDividers(float[] dividers) {
    this.dividers = dividers;
  }

  public int getNumberOfVerticalCompartments() {
    if (dividers != null) {
      return (dividers.length + 1);
    }
    return 1;
  }

  public void moveVerticalDivider(int i, float dx) {
    boolean adjust = true;
    if (owner != null && dividers != null) {
      int n = dividers.length;
      if (i >= 0 && i <= n - 1) {
	Point2D p1 = owner.getEnd1();
	Point2D p2 = owner.getEnd2();
	float x = (float) Math.min(p1.getX(), p2.getX());
	float w = (float) Math.abs(p1.getX() - p2.getX());
	float dx1 = dx / w;
	if ((dividers[i] + dx1) * w >= minCompartmentWidth) {
	  dividers[i] += dx1;
	} else {
	  dx1 = minCompartmentWidth / w - dividers[i];
	  dividers[i] = minCompartmentWidth / w;
	}

	float w1 = 0;
	int k;
	for (k = 0; k < i; k++) {
	  w1 += dividers[k] * w;
	}
	for (; k < n; k++) {
	  if (k > i && adjust) {
	    dividers[k] -= dx1;
	    if (dividers[k] * w < minCompartmentWidth) {
	      dividers[k] = minCompartmentWidth / w;
	      break;
	    }
	  }
	  float hmax = w - (n - k) * minCompartmentWidth;
	  if ((w1 + dividers[k] * w) > hmax) {
	    dividers[k] = (hmax - w1) / w;
	  }
	  w1 += dividers[k] * w;
	}
      }
    }
  }

  public void addVerticalDivider(int p, float dx) {
    if (dx > 0 && dx < 1) {
      if (dividers != null) {
	int n = dividers.length;
	if (p >= 0 && p <= n) {
	  float f = 1f - dx;
	  float[] newDiv = new float[n + 1];
	  for (int i = 0; i <= n; i++) {
	    if (i < p) {
	      newDiv[i] = dividers[i] * f;
	    } else if (i == p) {
	      newDiv[i] = dx;
	    } else {
	      newDiv[i] = dividers[i - 1] * f;
	    }
	  }
	  dividers = newDiv;
	}
      } else {
	if (p == 0) {
	  dividers = new float[1];
	  dividers[0] = dx;
	}
      }
    } else {
      if (dividers != null) {
	int n = dividers.length;
	if (p >= 0 && p <= n) {
	  float[] newDiv = new float[n + 1];
	  float xdiv = 0;
	  for (int i = 0; i <= n; i++) {
	    if (i < p) {
	      newDiv[i] = dividers[i];
	      xdiv += dividers[i];
	    } else if (i == p) {
	      if (i < n) {
		newDiv[i + 1] = newDiv[i] = dividers[i] / 2;
	      } else {
		newDiv[i] = (1 - xdiv) / 2;
	      }
	      i++;
	    } else {
	      newDiv[i] = dividers[i - 1];
	    }
	  }
	  dividers = newDiv;
	}
      } else {
	if (p == 0) {
	  dividers = new float[1];
	  dividers[0] = 0.5f;
	}
      }
    }
  }

  public void removeVerticalDivider(int p) {
    if (dividers != null) {
      int n = dividers.length;
      if (p >= 0 && p < n) {
	float dx = dividers[p];
	float f = 1f / (1f - dx);
	float[] newDiv = new float[n - 1];
	for (int i = 0; i < n - 1; i++) {
	  if (i < p) {
	    newDiv[i] = dividers[i] * f;
	  } else {
	    newDiv[i] = dividers[i + 1] * f;
	  }
	}
	dividers = newDiv;
      }
    }
  }

  public void drawVerticalDividers(Graphics2D g2) {
    if (owner != null && dividers != null) {
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      int gw = 0;
      if (owner instanceof RectangleShape) {
	RectangleShape rect = (RectangleShape) owner;
	if (rect.isShowVerticalGutter()) {
	  gw = rect.getGutterWidth();
	}
      }
      if (w <= 2 * gw) {
	gw = 0;
      }
      drawVerticalDividers(g2, x + gw, y, w - 2 * gw, h);
    }
  }

  public void drawVerticalDividers(Graphics2D g2, float x, float y, float w,
      float h) {
    if (dividers != null) {
      int n = dividers.length;
      float xdiv = 0;
      if (lineMode == DividerLineMode.Default) {
	for (int i = 0; i < n; i++) {
	  xdiv += dividers[i] * w;
	  if (xdiv > 0 && xdiv < h) {
	    g2.draw(new Line2D.Float(x + xdiv, y, x + xdiv, y + h));
	    // x, y + ydiv, x + w, y + ydiv));
	  }
	}
      } else {
	DrawAttr attr = owner.getDrawAttr();
	Stroke newStroke = null;
	StrokeMap smap = StrokeMap.getInstance();
	if (attr != null) {
	  newStroke = smap.findStroke(attr.getPenSize(), attr.getLineCap(),
	      attr.getLineJoin(), lineStyle);
	} else {
	  newStroke = smap.findStroke(1, BasicStroke.CAP_SQUARE,
	      BasicStroke.JOIN_MITER, lineStyle);
	}
	Stroke oldStroke = null;
	for (int i = 0; i < n; i++) {
	  if (lineMode == DividerLineMode.EveryLine && i == 0
	      || lineMode == DividerLineMode.EveryLineExceptFirst && i == 1) {
	    if (newStroke != null) {
	      oldStroke = g2.getStroke();
	      g2.setStroke(newStroke);
	    }
	  }
	  xdiv += dividers[i] * w;
	  if (xdiv > 0 && xdiv < h) {
	    g2.draw(new Line2D.Float(x + xdiv, y, x + xdiv, y + h));
	    // x, y + ydiv, x + w, y + ydiv));
	  }
	}
	if (oldStroke != null && newStroke != null) {
	  g2.setStroke(oldStroke);
	}
      }
    }
  }

  public void drawFrame(Graphics2D g2, float marksize, 
			boolean fill, Color frameColor) {
    if (owner != null && dividers != null) {
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float md = marksize / 2.0f;
      int n = dividers.length;
      float xdiv = 0;

      g2.setColor(frameColor);
      for (int i = 0; i < n; i++) {
	xdiv += dividers[i] * w;
	if (xdiv < w) {
	  Rectangle2D r = new Rectangle2D.Float(x + xdiv - md, y + h / 2 - md, marksize, marksize);
	  if (fill) { 
	    g2.fill(r);
	  } else { 
	    g2.draw(r);
	  }
	}
      }
    }
  }

  public int onVerticalDivider(float px, float py) {
    int result = -1;
    if (owner != null && dividers != null) {
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      if (px >= x - d && px <= x + w + d && py >= y - d && py <= y + h + d) {
	if (dividers != null) {
	  int n = dividers.length;
	  float xdiv = 0;
	  for (int i = 0; i < n; i++) {
	    xdiv += dividers[i] * w;
	    float dx = Math.abs(x + xdiv - px);
	    if (dx <= d) {
	      return PointOnVerticalDivider + i;
	    } else if (px < x + xdiv) {
	      return PointInVerticalCompartment + i;
	    }
	  }
	  result = PointInVerticalCompartment + n;
	}
      }
    }
    return result;
  }

  public int inVerticalCompartment(DrawShape s) {
    if (s != null && owner != null) {
      Point2D c = s.getCenterPosition();
      float cx = (float) c.getX();
      float cy = (float) c.getY();
      Point2D p1 = owner.getEnd1();
      Point2D p2 = owner.getEnd2();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      if (cx >= x - d && cx <= x + w + d && cy >= y - d && cy <= y + h + d) {
	if (dividers != null) {
	  int n = dividers.length;
	  float xdiv = 0;
	  for (int i = 0; i < n; i++) {
	    xdiv += dividers[i] * w;
	    if (cx < x + xdiv) {
	      return i;
	    }
	  }
	  return n;
	} else {
	  return 0;
	}
      }
    }
    return -1;
  }

  public boolean isNumberOfVerticalCompartmentsFixed() {
    return compartmentNumberFixed;
  }

  public void setNumberOfVerticalCompartmentsFixed(boolean b) {
    compartmentNumberFixed = b;
  }

  public void writeXML(CodeWriter out, String namespacePrefix) {
    if (out != null && dividers != null) {
      String divname = DrawShapeBase.getPrefixedName("VDividers",
	  namespacePrefix);
      int n = dividers.length;
      out.print("<" + divname + ' ' + getXMLAttributeString("ndiv", n) + ' ' + 
		getXMLAttributeString("fixed", compartmentNumberFixed) + ' ' + 
		getXMLAttributeString("mode", lineMode) + ' ' +
		getXMLAttributeString("highlight", highlight) + ' ' + 
		getXMLAttributeString("expandable", expandable) + ' ' + 
		getXMLAttributeString("collapsable", collapsable) + ' ' + 
		getXMLAttributeString("style", lineStyle) + ">");
      for (int i = 0; i < n; i++) {
	if (i > 0) {
	  out.print(" ");
	}
	out.print(Float.toString(dividers[i]));
      }
      out.println("</" + divname + ">");
    }
  }

  public void setAttrFromXML(Element node, String namespaceURI) {
    if (node != null) {
      String uri = node.getNamespaceURI();
      if (namespaceURI == null || namespaceURI.equals(uri)) {
	String s = node.getAttribute("ndiv");
	int ndiv = 0;
	try {
	  ndiv = Integer.parseInt(s);
	} catch (NumberFormatException ex) {
	}

	if (ndiv > 0) {
	  dividers = new float[ndiv];

	  String val = XMLUtil.getTextChildNodeValue(node);
	  StringTokenizer st = new StringTokenizer(val, " ");
	  for (int j = 0; j < ndiv && st.hasMoreTokens(); j++) {
	    String fval = st.nextToken();
	    if (fval != null && fval.length() > 0) {
	      try {
		dividers[j] = Float.parseFloat(fval);
	      } catch (NumberFormatException ex) {
	      }
	    }
	  }
	}

	s = node.getAttribute("fixed");
	try {
	  compartmentNumberFixed = Boolean.valueOf(s);
	} catch (IllegalArgumentException ex) {
	}

	s = node.getAttribute("mode");
	try {
	  lineMode = Enum.valueOf(DividerLineMode.class, s);
	} catch (IllegalArgumentException ex) {
	}

	s = node.getAttribute("style");
	try {
	  lineStyle = Integer.parseInt(s);
	} catch (NumberFormatException ex) {
	}

	s = node.getAttribute("highlight ");
	try {
	  highlight = Boolean.valueOf(s);
	} catch (NumberFormatException ex) {
	}

	s = node.getAttribute("expandable");
	try {
	  expandable = Boolean.valueOf(s);
	} catch (NumberFormatException ex) {
	}

	s = node.getAttribute("collapsable");
	try {
	  collapsable = Boolean.valueOf(s);
	} catch (NumberFormatException ex) {
	}
      }
    }
  }

  protected DrawShape owner;

  protected float[] dividers;

  /*
   * n = dividers.length, width is the hieght of the rectangle there are n +
   * 1 compartments inside the rectangle 0 < dividers[i] < 1; (the
   * fraction of the width) Sum dividers[0 .. n-1] < 1 the width of the
   * i-th compartment is dividers[i] * width the width of the (n+1)-th
   * compartment is (Sum dividers[0 .. n-1]) * width
   */
  protected DividerLineMode lineMode;

  protected int lineStyle;

  protected boolean highlight = false;

  protected boolean expandable = true;

  protected boolean collapsable = true;

  protected float minCompartmentWidth = 4;

  protected boolean compartmentNumberFixed = false;

}
